Coin Change
LeetCode 322 | Difficulty: Mediumβ
Problem Descriptionβ
You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
You may assume that you have an infinite number of each kind of coin.
Constraintsβ
1 <= coins.length <= 121 <= coins[i] <= 2^31 - 10 <= amount <= 10^4
Examplesβ
Example 1:
Input: coins = [1,2,5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3
Output: -1
Explanation: Cannot make 3 with only coin denomination 2.
Example 3:
Input: coins = [1], amount = 0
Output: 0
Explanation: No coins needed for amount 0.
Approachβ
This is a classic unbounded knapsack problem solvable with dynamic programming.
Key Insight: For each amount i, the minimum coins needed is:
$$dp[i] = \min(dp[i - coin_j] + 1) \text{ for all coins } j$$
DP State Definition:
dp[i]= minimum number of coins needed to make amounti- Base case:
dp[0] = 0(0 coins needed for amount 0)
DP Table for Example 1 (coins = [1, 2, 5], amount = 11):
| Amount | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp[i] | 0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 |
Trace for dp[11]:
- Using coin 1:
dp[10] + 1 = 2 + 1 = 3 - Using coin 2:
dp[9] + 1 = 3 + 1 = 4 - Using coin 5:
dp[6] + 1 = 2 + 1 = 3 - Minimum: 3 (5 + 5 + 1)
Complexity Analysisβ
- Time Complexity: $O(S \times N)$ β where $S$ is the amount and $N$ is the number of coin denominations
- Space Complexity: $O(S)$ β for the DP array
Solution: Bottom-Up DP (Recommended)β
public int CoinChange(int[] coins, int amount)
{
if (coins.Length == 0) return -1;
if (amount == 0) return 0;
int[] dp = new int[amount + 1];
Array.Fill(dp, amount + 1); // Use amount+1 as "infinity"
dp[0] = 0;
for (int i = 1; i <= amount; i++)
{
foreach (int coin in coins)
{
if (coin <= i && dp[i - coin] != amount + 1)
{
dp[i] = Math.Min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
Solution: Top-Down DP with Memoizationβ
public int CoinChange(int[] coins, int amount)
{
if (amount == 0) return 0;
int[] memo = new int[amount + 1];
Array.Fill(memo, -2); // -2 means not computed
return CoinChangeHelper(coins, amount, memo);
}
private int CoinChangeHelper(int[] coins, int amount, int[] memo)
{
if (amount < 0) return -1;
if (amount == 0) return 0;
if (memo[amount] != -2) return memo[amount];
int minCoins = int.MaxValue;
foreach (int coin in coins)
{
int result = CoinChangeHelper(coins, amount - coin, memo);
if (result >= 0)
{
minCoins = Math.Min(minCoins, result + 1);
}
}
memo[amount] = (minCoins == int.MaxValue) ? -1 : minCoins;
return memo[amount];
}
Solution: BFS Approachβ
Think of it as finding the shortest path from amount to 0:
public int CoinChangeBFS(int[] coins, int amount)
{
if (amount == 0) return 0;
Queue<int> queue = new Queue<int>();
HashSet<int> visited = new HashSet<int>();
queue.Enqueue(amount);
visited.Add(amount);
int level = 0;
while (queue.Count > 0)
{
level++;
int size = queue.Count;
for (int i = 0; i < size; i++)
{
int curr = queue.Dequeue();
foreach (int coin in coins)
{
int next = curr - coin;
if (next == 0) return level;
if (next > 0 && !visited.Contains(next))
{
visited.Add(next);
queue.Enqueue(next);
}
}
}
}
return -1;
}
Interview Tipsβ
- Why Bottom-Up over Top-Down? β Avoids recursion stack overhead; better cache locality
- Greedy doesn't work! β For coins = [1, 3, 4] and amount = 6, greedy gives 4+1+1 (3 coins) but optimal is 3+3 (2 coins)
- Edge Cases:
amount = 0β return 0- No valid combination β return -1
- Single coin equals amount β return 1
Related Problemsβ
- 518. Coin Change II β Count the number of combinations (not minimum)
- 279. Perfect Squares β Same pattern with squares as "coins"
- 983. Minimum Cost For Tickets β DP with variable coin sizes
- 377. Combination Sum IV β Count permutations instead of combinations